package com.yc.cloud.business;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yc.cloud.common.model.response.Outcome;
import com.yc.cloud.common.utils.RedisUtil;
import com.yc.cloud.log.feign.LogClient;
import com.yc.cloud.log.model.FindDataLogPage;
import com.yc.cloud.log.model.DataLog;
import com.yc.cloud.model.UserInfo;
import com.yc.cloud.model.UserLog;
import com.yc.cloud.model.find.FindLogPage;
import com.yc.cloud.model.find.FindUserPage;
import com.yc.cloud.model.save.SaveUser;
import com.yc.cloud.model.update.UpdatePassword;
import com.yc.cloud.model.update.UpdateUser;
import com.yc.cloud.sys.entity.*;
import com.yc.cloud.sys.service.*;
import com.yc.cloud.utils.PasswordUtil;
import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.yc.cloud.common.constant.BaseConstant.*;
import static com.yc.cloud.common.constant.RedisConstant.SYS_USER_CACHE;
import static com.yc.cloud.common.constant.RedisConstant.SYS_USER_LOGIN;
import static java.lang.Boolean.TRUE;

/**
 * @author 杨智杰
 * @date 2021/8/13 8:44
 */
@Service
public class SysUserBusiness {

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private LogClient logClient;

    @Resource
    private SysUserService sysUserService;

    @Resource
    private SysRoleService sysRoleService;

    @Resource
    private SysMenuService sysMenuService;

    @Resource
    private Ip2regionSearcher regionSearcher;

    @Resource
    private SysRoleMenuService sysRoleMenuService;

    @Resource
    private SysUserRoleService sysUserRoleService;

    @Resource
    private SysDepartmentService sysDepartmentService;

    @Resource
    private SysDictionariesBusiness sysDictionariesBusiness;

    @Resource
    private SysUserDepartmentService sysUserDepartmentService;

    /**
     * 用户缓存过期时长,默认25小时
     */
    @Value("${cache-config.sys-user.valid-time:90000}")
    private Long userCacheValidTime;

    /**
     * 根据登录名获取用户信息
     * 优先从缓存中获取,未查询到再从数据库获取
     * 如果从数据库中获得用户信息并该用户处于正常状态就加入到缓存中
     *
     * @param loginName 登录账号
     * @return
     */
    public SysUser getUserByLoginName(String loginName) {
        SysUser user = (SysUser) redisUtil.get(SYS_USER_CACHE + loginName);
        if (ObjectUtil.isNull(user)) {
            user = sysUserService.getOne(new QueryWrapper<SysUser>().lambda()
                    .eq(SysUser::getLoginName, loginName).last(MYSQL_LIMIT_ONE));
            if (ObjectUtil.isNotNull(user) && ONE == user.getStatus()) {
                redisUtil.set(SYS_USER_CACHE + loginName, user, userCacheValidTime);
            }
        }
        return user;
    }

    /**
     * 根据token从redis中获取用户详细信息
     *
     * @param token
     * @return
     */
    public UserInfo getUserInfo(String token) {
        return (UserInfo) redisUtil.get(SYS_USER_LOGIN + token);
    }

    /**
     * 根据用户Id获取用户详细信息
     *
     * @param id 用户Id
     * @return 详细信息
     */
    public UserInfo getUserInfo(Long id) {
        return getUserInfo(sysUserService.getById(id));
    }

    /**
     * 获取当前登录用户信息
     *
     * @return
     */
    public UserInfo getUserInfo() {
        return getUserInfo(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest().getHeader(AUTHORIZATION));
    }

    /**
     * 根据用户基础信息获取用户详细信息
     *
     * @param user
     * @return
     */
    public UserInfo getUserInfo(SysUser user) {
        if (ObjectUtil.isNull(user)) {
            return null;
        }
        UserInfo userInfo = Convert.convert(UserInfo.class, user);
        List<SysUserDepartment> userDepartmentList = sysUserDepartmentService.list(new QueryWrapper<SysUserDepartment>().lambda()
                .eq(SysUserDepartment::getUserId, user.getId()));
        List<SysUserRole> userRoleList = sysUserRoleService.list(new QueryWrapper<SysUserRole>().lambda()
                .eq(SysUserRole::getUserId, user.getId()));
        List<SysRoleMenu> roleMenuList = CollUtil.isNotEmpty(userRoleList) ? sysRoleMenuService.list(new QueryWrapper<SysRoleMenu>().lambda()
                .in(SysRoleMenu::getRoleId, userRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toSet()))) : null;
        List<SysMenu> menuList = CollUtil.isNotEmpty(roleMenuList) ? sysMenuService.list(new QueryWrapper<SysMenu>().lambda()
                .in(SysMenu::getId, roleMenuList.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toSet()))) : null;
        userInfo
                .setDeptList(CollUtil.isNotEmpty(userDepartmentList) ? sysDepartmentService.list(new QueryWrapper<SysDepartment>().lambda()
                        .in(SysDepartment::getId, userDepartmentList.stream().map(SysUserDepartment::getDeptId).collect(Collectors.toSet()))) : null)
                .setRoleList(CollUtil.isNotEmpty(userRoleList) ? sysRoleService.list(new QueryWrapper<SysRole>().lambda()
                        .in(SysRole::getId, userRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toSet()))) : null)
                .setAuthStrList(CollUtil.isNotEmpty(menuList) ? menuList.stream().map(SysMenu::getAuthStr).collect(Collectors.toList()) : null);
        userInfo.setPassword("******").setSale("******");
        return userInfo;
    }

    /**
     * 判断用户是不是admin
     *
     * @param param
     * @return
     */
    public Boolean isAdmin(UserInfo param) {
        return CollUtil.isEmpty(param.getAuthStrList()) ? false
                : param.getAuthStrList().stream().anyMatch(auth -> ADMIN.equals(auth));
    }

    /**
     * 根据用户Id判断用户是不是admin
     *
     * @param id
     * @return
     */
    public Boolean isAdmin(Long id) {
        return isAdmin(getUserInfo(id));
    }

    /**
     * 根据用户信息判断用户是不是admin
     *
     * @param param
     * @return
     */
    public Boolean isAdmin(SysUser param) {
        return isAdmin(getUserInfo(param));
    }

    /**
     * 判断当前登录用户是不是admin
     *
     * @return
     */
    public Boolean isAdmin() {
        return isAdmin(getUserInfo());
    }

    /**
     * 根据Id修改用户信息
     *
     * @param user 用户信息
     * @return 是否成功
     */
    public Boolean update(SysUser user) {
        return sysUserService.updateById(user);
    }

    /**
     * 重置密码
     */
    @Value("${user-config.password.rest:123456}")
    private String restPassword;

    /**
     * 弱密码检查
     */
    @Value("${user-config.password.enable-easy-check:false}")
    private Boolean easyCheck;

    /**
     * 重置用户密码
     *
     * @param id 用户Id
     * @return 是否成功
     */
    public Boolean restPassword(Long id) {
        UserInfo loginUser = getUserInfo();
        SysUser su = sysUserService.getById(id);
        if (ObjectUtil.isNotNull(su)) {
            String sale = RandomUtil.randomString(FOUR);
            String password = PasswordUtil.getPassWord(sale, SecureUtil.md5(restPassword));
            SysUser user = new SysUser().setPassword(password).setSale(sale);
            user.setId(id).setUpdateBy(loginUser.getId());
            redisUtil.del(SYS_USER_CACHE + su.getLoginName());
            return sysUserService.updateById(user);
        }
        return false;
    }

    /**
     * 用户修改密码
     *
     * @param param 新旧密码
     * @return 是否成功
     */
    public Outcome updatePassword(UpdatePassword param) {
        UserInfo loginUser = getUserInfo();
        SysUser user = sysUserService.getById(loginUser.getId());
        if (user.getPassword().equals(PasswordUtil.getPassWord(user.getSale(), param.getOldPassword()))) {
            if (easyCheck) {
                String easyPasswordJson = ResourceUtil.readUtf8Str("EasyPassword.json");
                JSONArray array = JSONUtil.parseArray(easyPasswordJson);
                if (array.stream().anyMatch(ep -> param.getNewPassword().equals(SecureUtil.md5((String) ep)))) {
                    return Outcome.failure("密码过于简单!");
                }
            }
            String sale = RandomUtil.randomString(FOUR);
            String password = PasswordUtil.getPassWord(sale, param.getNewPassword());
            user = new SysUser().setSale(sale).setPassword(password);
            user.setId(loginUser.getId()).setUpdateBy(loginUser.getId());
            redisUtil.del(SYS_USER_CACHE + loginUser.getLoginName());
            return Outcome.status(sysUserService.updateById(user));
        }
        return Outcome.failure("旧密码校验失败!");
    }

    /**
     * 新增用户
     *
     * @param param 用户信息
     * @return 是否成功
     */
    @Transactional(rollbackFor = Exception.class)
    public Outcome save(SaveUser param) {
        SysUser sysUser = Convert.convert(SysUser.class, param);
        String sale = RandomUtil.randomString(FOUR);
        String password = PasswordUtil.getPassWord(sale, param.getPassword());
        sysUser.setPassword(password).setSale(sale).setCreateBy(getUserInfo().getId());
        if (sysUserService.count(new QueryWrapper<SysUser>().lambda()
                .eq(SysUser::getLoginName, param.getLoginName())) > ZERO) {
            return Outcome.failure("登录账号已被占用!");
        }
        if (StrUtil.isNotBlank(param.getPhone())
                && sysUserService.count(new QueryWrapper<SysUser>().lambda()
                .eq(SysUser::getPhone, param.getPhone())) > ZERO) {
            return Outcome.failure("手机号已被占用!");
        }
        if (StrUtil.isNotBlank(param.getEmail())
                && sysUserService.count(new QueryWrapper<SysUser>().lambda()
                .eq(SysUser::getEmail, param.getEmail())) > ZERO) {
            return Outcome.failure("该邮箱已被占用!");
        }
        boolean save = sysUserService.save(sysUser);
        if (save) {
            List<SysRole> sysRoleList;
            List<SysDepartment> sysDepartmentList;
            if (CollUtil.isEmpty(param.getRoleList())) {
                sysRoleList = sysRoleService.list(new QueryWrapper<SysRole>().lambda()
                        .eq(SysRole::getStatus, ONE).eq(SysRole::getIsDefault, TRUE));
                if (CollUtil.isNotEmpty(sysRoleList)) {
                    sysUserRoleService.saveBatch(sysRoleList.stream().map(r -> new SysUserRole()
                            .setUserId(sysUser.getId()).setRoleId(r.getId())).collect(Collectors.toList()));
                }
            } else {
                sysUserRoleService.saveBatch(sysRoleService.list(new QueryWrapper<SysRole>().lambda().eq(SysRole::getStatus, ONE))
                        .stream().filter(sr -> param.getRoleList().contains(sr.getId())).collect(Collectors.toList())
                        .stream().map(r -> new SysUserRole().setUserId(sysUser.getId()).setRoleId(r.getId()))
                        .collect(Collectors.toList()));
            }
            if (CollUtil.isEmpty(param.getDeptList())) {
                sysDepartmentList = sysDepartmentService.list(new QueryWrapper<SysDepartment>().lambda()
                        .eq(SysDepartment::getStatus, ONE).eq(SysDepartment::getIsDefault, TRUE));
                if (CollUtil.isNotEmpty(sysDepartmentList)) {
                    sysUserDepartmentService.saveBatch(sysDepartmentList.stream().map(r -> new SysUserDepartment()
                            .setUserId(sysUser.getId()).setDeptId(r.getId())).collect(Collectors.toList()));
                }
            } else {
                sysUserDepartmentService.saveBatch(sysDepartmentService.list(new QueryWrapper<SysDepartment>().lambda().eq(SysDepartment::getStatus, ONE))
                        .stream().filter(sd -> param.getDeptList().contains(sd.getId())).collect(Collectors.toList())
                        .stream().map(r -> new SysUserDepartment().setUserId(sysUser.getId()).setDeptId(r.getId()))
                        .collect(Collectors.toList()));
            }
        }
        return Outcome.status(save);
    }

    /**
     * 修改用户信息
     *
     * @param param 用户信息
     * @return 是否成功
     */
    public Outcome update(UpdateUser param) {
        SysUser sysUser = Convert.convert(SysUser.class, param);
        SysUser oldUser = sysUserService.getById(param.getId());
        if (ObjectUtil.isNull(oldUser)) {
            return Outcome.failure("用户不存在!");
        }
        if (StrUtil.isNotBlank(param.getPhone())
                && sysUserService.count(new QueryWrapper<SysUser>().lambda()
                .eq(SysUser::getPhone, param.getPhone())
                .ne(SysUser::getId, param.getId())) > ZERO) {
            return Outcome.failure("手机号已被占用!");
        }
        if (StrUtil.isNotBlank(param.getEmail())
                && sysUserService.count(new QueryWrapper<SysUser>().lambda()
                .eq(SysUser::getEmail, param.getEmail())
                .ne(SysUser::getId, param.getId())) > ZERO) {
            return Outcome.failure("该邮箱已被占用!");
        }
        sysUser.setCreateBy(getUserInfo().getId());
        boolean update = sysUserService.updateById(sysUser);
        if (update) {
            refreshUserInfo(param.getId());
        }
        return Outcome.status(update);
    }

    /**
     * 删除
     *
     * @param id 根据Id删除
     * @return 是否成功
     */
    @Transactional(rollbackFor = Exception.class)
    public Boolean delete(List<Long> id) {
        UserInfo userInfo = getUserInfo();
        List<SysUser> userList = sysUserService.listByIds(id);
        userList.stream().forEach(user -> redisUtil.del(SYS_USER_CACHE + user.getLoginName()));
        boolean updateBatchById = sysUserService.updateBatchById(Convert.toList(SysUser.class, id.stream()
                .map(i -> new SysUser().setUpdateBy(userInfo.getId()).setId(i)).collect(Collectors.toList())));
        boolean removeByIds = sysUserService.removeByIds(id);
        return updateBatchById && removeByIds;
    }

    /**
     * 分页
     *
     * @param param 查询条件
     * @return 分页数据
     */
    public IPage<SysUser> page(FindUserPage param) {
        List<SysUserDepartment> suList;
        if (ObjectUtil.isNotNull(param.getDeptId())) {
            suList = sysUserDepartmentService.list(new QueryWrapper<SysUserDepartment>().lambda()
                    .eq(SysUserDepartment::getDeptId, param.getDeptId()));
            if (CollUtil.isEmpty(suList)) {
                suList.add(new SysUserDepartment().setUserId(-894811087L).setDeptId(-894811087L));
            }
        } else {
            suList = new ArrayList<>();
        }
        IPage<SysUser> userIPage = sysUserService.page(new Page<>(param.getCurrent(), param.getSize()),
                new QueryWrapper<SysUser>().lambda()
                        .eq(ObjectUtil.isNotNull(param.getId()), SysUser::getId, param.getId())
                        .like(StrUtil.isNotBlank(param.getPhone()), SysUser::getPhone, param.getPhone())
                        .like(StrUtil.isNotBlank(param.getEmail()), SysUser::getEmail, param.getEmail())
                        .eq(ObjectUtil.isNotNull(param.getStatus()), SysUser::getStatus, param.getStatus())
                        .like(StrUtil.isNotBlank(param.getUserName()), SysUser::getUserName, param.getUserName())
                        .like(StrUtil.isNotBlank(param.getLoginName()), SysUser::getLoginName, param.getLoginName())
                        .in(ObjectUtil.isNotNull(param.getDeptId()) && CollUtil.isNotEmpty(suList),
                                SysUser::getId, suList.stream().map(SysUserDepartment::getUserId).collect(Collectors.toSet()))
                        .orderByDesc(SysUser::getCreateTime));
        if (CollUtil.isNotEmpty(userIPage.getRecords())) {
            userIPage.getRecords().stream().forEach(su -> su.setPassword(null).setSale(null));
        }
        return userIPage;
    }

    /**
     * 刷新用户缓存信息
     *
     * @param userId 用户Id
     */
    public void refreshUserInfo(Long userId) {
        SysUser sysUser = sysUserService.getById(userId);
        redisUtil.del(SYS_USER_CACHE + sysUser.getLoginName());
        UserInfo userInfo = getUserInfo(sysUser.getId());
        Set<String> userToken = redisUtil.dimGetKey(SYS_USER_LOGIN + "*." + SecureUtil.md5(sysUser.getLoginName()) + ".*");
        if (CollUtil.isNotEmpty(userToken)) {
            for (String token : userToken) {
                redisUtil.set(token, userInfo, redisUtil.getExpire(token));
            }
        }
    }

    /**
     * 用户日志查询
     *
     * @param param
     * @return
     */
    public IPage<UserLog> logPage(FindLogPage param) {
        if (!isAdmin()) {
            param.setUserId(getUserInfo().getId());
        }
        SysUser sysUser = null;
        if (ObjectUtil.isNotNull(param.getUserId())) {
            sysUser = sysUserService.getById(param.getUserId());
        }
        FindDataLogPage findDataLogPage = Convert.convert(FindDataLogPage.class, param);
        if (ObjectUtil.isNotNull(sysUser)) {
            findDataLogPage.setLoginName(sysUser.getLoginName());
        }
        Page<DataLog> data = logClient.page(BeanUtil.beanToMap(findDataLogPage, false, true)).getData();
        List<UserLog> logList = new ArrayList<>();
        data.getRecords().stream().forEach(dataLog -> {
            UserLog userLog = new UserLog().setLogId(dataLog.getId()).setAnswer(dataLog.getAnswer()).setMethod(dataLog.getMethod())
                    .setUri(dataLog.getRequestURI()).setHost(dataLog.getRemoteHost()).setCreateTime(dataLog.getCreateTime())
                    .setAdder(regionSearcher.getAddress(dataLog.getRemoteHost()));
            List<SysDictionaries> list = sysDictionariesBusiness.list("logger", dataLog.getRequestURI());
            if (CollUtil.isNotEmpty(list)) {
                list = list.stream().filter(sd -> sd.getKeyword().equals(dataLog.getRequestURI())
                        && sd.getParam().equals(dataLog.getMethod())).collect(Collectors.toList());
            }
            userLog.setState(CollUtil.isNotEmpty(list) ? list.get(ZERO).getState() : dataLog.getRequestURI());
            logList.add(userLog);
        });
        IPage<UserLog> logPage = Convert.convert(Page.class, data);
        logPage.setRecords(logList);
        return logPage;
    }
}
